﻿// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

#nullable disable

using System.Collections;
using System.Drawing;
using System.Windows.Forms.Design.Behavior;

namespace System.Windows.Forms.Design;

/// <summary>
///  Transparent Window to parent the DropDowns.
/// </summary>
internal sealed class ToolStripAdornerWindowService : IDisposable
{
    private readonly ToolStripAdornerWindow _toolStripAdornerWindow; // the transparent window all glyphs are drawn to
    private BehaviorService _behaviorService;
    private Adorner _dropDownAdorner;
    private ArrayList _dropDownCollection;
    private readonly IOverlayService _overlayService;

    /// <summary>
    ///  This constructor is called from DocumentDesigner's Initialize method.
    /// </summary>
    internal ToolStripAdornerWindowService(IServiceProvider serviceProvider, Control windowFrame)
    {
        // Create the AdornerWindow
        _toolStripAdornerWindow = new ToolStripAdornerWindow(windowFrame);
        _behaviorService = (BehaviorService)serviceProvider.GetService(typeof(BehaviorService));
        int indexToInsert = _behaviorService.AdornerWindowIndex;

        // Use the adornerWindow as an overlay
        _overlayService = (IOverlayService)serviceProvider.GetService(typeof(IOverlayService));
        _overlayService?.InsertOverlay(_toolStripAdornerWindow, indexToInsert);

        _dropDownAdorner = new Adorner();
        int count = _behaviorService.Adorners.Count;

        // Why this is NEEDED ?  To Add the Adorner at proper index in the AdornerCollection for the BehaviorService. So that the DesignerActionGlyph always stays on the Top.
        if (count > 1)
        {
            _behaviorService.Adorners.Insert(count - 1, _dropDownAdorner);
        }
    }

    /// <summary>
    ///  Returns the actual Control that represents the transparent AdornerWindow.
    /// </summary>
    internal Control ToolStripAdornerWindowControl
    {
        get => _toolStripAdornerWindow;
    }

    /// <summary>
    ///  Creates and returns a Graphics object for the AdornerWindow
    /// </summary>
    public Graphics ToolStripAdornerWindowGraphics
    {
        get => _toolStripAdornerWindow.CreateGraphics();
    }

    internal Adorner DropDownAdorner
    {
        get => _dropDownAdorner;
    }

    /// <summary>
    ///  Disposes the behavior service.
    /// </summary>
    public void Dispose()
    {
        _overlayService?.RemoveOverlay(_toolStripAdornerWindow);

        _toolStripAdornerWindow.Dispose();
        if (_behaviorService is not null)
        {
            _behaviorService.Adorners.Remove(_dropDownAdorner);
            _behaviorService = null;
        }

        if (_dropDownAdorner is not null)
        {
            _dropDownAdorner.Glyphs.Clear();
            _dropDownAdorner = null;
        }
    }

    /// <summary>
    ///  Translates a point in the AdornerWindow to screen coords.
    /// </summary>
    public Point AdornerWindowPointToScreen(Point p) => _toolStripAdornerWindow.PointToScreen(p);

    /// <summary>
    ///  Gets the location (upper-left corner) of the AdornerWindow in screen coords.
    /// </summary>
    public Point AdornerWindowToScreen() => AdornerWindowPointToScreen(new Point(0, 0));

    /// <summary>
    ///  Returns the location of a Control translated to AdornerWidnow coords.
    /// </summary>
    public Point ControlToAdornerWindow(Control c)
    {
        if (c.Parent is null)
        {
            return Point.Empty;
        }

        Point pt = new(c.Left, c.Top);
        PInvoke.MapWindowPoints(c.Parent, _toolStripAdornerWindow, ref pt);
        return pt;
    }

    /// <summary>
    ///  Invalidates the BehaviorService's AdornerWindow.  This will force a refresh of all Adorners and, in turn, all Glyphs.
    /// </summary>
    public void Invalidate()
    {
        _toolStripAdornerWindow.InvalidateAdornerWindow();
    }

    /// <summary>
    ///  Invalidates the BehaviorService's AdornerWindow.  This will force a refresh of all Adorners and, in turn, all Glyphs.
    /// </summary>
    public void Invalidate(Rectangle rect)
    {
        _toolStripAdornerWindow.InvalidateAdornerWindow(rect);
    }

    /// <summary>
    ///  Invalidates the BehaviorService's AdornerWindow.  This will force a refresh of all Adorners and, in turn, all Glyphs.
    /// </summary>
    public void Invalidate(Region r)
    {
        _toolStripAdornerWindow.InvalidateAdornerWindow(r);
    }

    internal ArrayList DropDowns
    {
        get => _dropDownCollection;
        set
        {
            _dropDownCollection ??= [];
        }
    }

    /// <summary>
    ///  ControlDesigner calls this internal method in response to a WmPaint. We need to know when a ControlDesigner paints - 'cause we will need to re-paint any glyphs above of this Control.
    /// </summary>
    internal void ProcessPaintMessage(Rectangle paintRect)
    {
        // Note, we don't call BehSvc.Invalidate because this will just cause the messages to recurse. Instead, invalidating this adornerWindow will just cause a "propagatePaint" and draw the glyphs.
        _toolStripAdornerWindow.Invalidate(paintRect);
    }

    /// <summary>
    ///  The AdornerWindow is a transparent window that resides ontop of the Designer's Frame.  This window is used by the ToolStripAdornerWindowService to parent the MenuItem DropDowns.
    /// </summary>
    private class ToolStripAdornerWindow : Control
    {
        private Control _designerFrame; // the designer's frame

        internal ToolStripAdornerWindow(Control designerFrame)
        {
            _designerFrame = designerFrame;
            Dock = DockStyle.Fill;
            AllowDrop = true;
            Text = "ToolStripAdornerWindow";
            SetStyle(ControlStyles.Opaque, true);
        }

        /// <summary>
        ///  The key here is to set the appropriate TransparentWindow style.
        /// </summary>
        protected override CreateParams CreateParams
        {
            get
            {
                CreateParams cp = base.CreateParams;
                cp.Style &= ~(int)(WINDOW_STYLE.WS_CLIPCHILDREN | WINDOW_STYLE.WS_CLIPSIBLINGS);
                cp.ExStyle |= (int)WINDOW_EX_STYLE.WS_EX_TRANSPARENT;
                return cp;
            }
        }

        /// <summary>
        ///  We'll use CreateHandle as our notification for creating our mouse attacher.
        /// </summary>
        protected override void OnHandleCreated(EventArgs e)
        {
            base.OnHandleCreated(e);
        }

        /// <summary>
        ///  Unhook and null out our mouseHook.
        /// </summary>
        protected override void OnHandleDestroyed(EventArgs e)
        {
            base.OnHandleDestroyed(e);
        }

        /// <summary>
        ///  Null out our mouseHook and unhook any events.
        /// </summary>
        protected override void Dispose(bool disposing)
        {
            if (disposing)
            {
                if (_designerFrame is not null)
                {
                    _designerFrame = null;
                }
            }

            base.Dispose(disposing);
        }

        /// <summary>
        ///  Returns true if the DesignerFrame is created and not being disposed.
        /// </summary>
        private bool DesignerFrameValid
        {
            get
            {
                if (_designerFrame is null || _designerFrame.IsDisposed || !_designerFrame.IsHandleCreated)
                {
                    return false;
                }

                return true;
            }
        }

        /// <summary>
        ///  Invalidates the transparent AdornerWindow by asking the Designer Frame beneath it to invalidate.  Note the they use of the .Update() call for perf. purposes.
        /// </summary>
        internal void InvalidateAdornerWindow()
        {
            if (DesignerFrameValid)
            {
                _designerFrame.Invalidate(true);
                _designerFrame.Update();
            }
        }

        /// <summary>
        ///  Invalidates the transparent AdornerWindow by asking the Designer Frame beneath it to invalidate.  Note the they use of the .Update() call for perf. purposes.
        /// </summary>
        internal void InvalidateAdornerWindow(Region region)
        {
            if (DesignerFrameValid)
            {
                _designerFrame.Invalidate(region, true);
                _designerFrame.Update();
            }
        }

        /// <summary>
        ///  Invalidates the transparent AdornerWindow by asking the Designer Frame beneath it to invalidate.  Note the they use of the .Update() call for perf. purposes.
        /// </summary>
        internal void InvalidateAdornerWindow(Rectangle rectangle)
        {
            if (DesignerFrameValid)
            {
                _designerFrame.Invalidate(rectangle, true);
                _designerFrame.Update();
            }
        }

        /// <summary>
        ///  The AdornerWindow intercepts all designer-related messages and forwards them to the BehaviorService for appropriate actions.  Note that Paint and HitTest  messages are correctly parsed and translated to AdornerWindow coords.
        /// </summary>
        protected override void WndProc(ref Message m)
        {
            switch (m.MsgInternal)
            {
                case PInvoke.WM_NCHITTEST:
                    m.ResultInternal = (LRESULT)PInvoke.HTTRANSPARENT;
                    break;
                default:
                    base.WndProc(ref m);
                    break;
            }
        }
    }
}
